/*
 Copyright 2017, 2018 IBM All Rights Reserved.

 SPDX-License-Identifier: Apache-2.0

*/

'use strict';

const {Key} = require('../../../');
const jsrsa = require('jsrsasign');
const asn1 = jsrsa.asn1;

const elliptic = require('elliptic');
const EC = elliptic.ec;


/**
 * This module implements the {@link module:api.Key} interface, for ECDSA key management
 * by Hardware Security Module support via PKCS#11 interface.
 *
 * @class PKCS11_ECDSA_KEY
 * @extends module:api.Key
 */
const PKCS11_ECDSA_KEY = class extends Key {

	constructor(attr, size) {
		if (typeof attr === 'undefined' || attr === null) {
			throw new Error('constructor: attr parameter must be specified');
		}
		if (typeof attr.ski === 'undefined' || attr.ski === null) {
			throw new Error('constructor: invalid key SKI');
		}
		if (!(attr.ski instanceof Buffer)) {
			throw new Error('constructor: key SKI must be Buffer type');
		}
		if ((typeof attr.priv === 'undefined' || attr.priv === null) &&
			(typeof attr.pub === 'undefined' || attr.pub === null)) {
			throw new Error('constructor: invalid key handles');
		}
		if (typeof attr.priv !== 'undefined' && attr.priv !== null &&
			!(attr.priv instanceof Buffer)) {
			throw new Error('constructor: private key handle must be Buffer type');
		}
		if (typeof attr.pub !== 'undefined' && attr.pub !== null &&
			!(attr.pub instanceof Buffer)) {
			throw new Error('constructor: public key handle must be Buffer type');
		}
		if (size === 'undefined') {
			throw new Error('constructor: size parameter must be specified');
		}
		if (size !== 256 && size !== 384) {
			throw new Error(
				'constructor: only 256 or 384 bits key size is supported');
		}

		super();

		/*
		 * Common for both private and public key.
		 */
		this._ski = attr.ski;
		this._size = size;

		/*
		 * private key: ski set, ecpt set, priv set,   pub set
		 * public  key: ski set, ecpt set, priv unset, pub set
		 */
		if (typeof attr.priv !== 'undefined' && attr.priv !== null) {
			this._handle = attr.priv;
			this._pub = new PKCS11_ECDSA_KEY(
				{ski: attr.ski, ecpt: attr.ecpt, pub: attr.pub}, size);
		} else {
			this._ecpt = attr.ecpt;
			this._handle = attr.pub;
			this._pub = null;
		}
	}

	signCSR(csr, sigAlgName) {

		csr.asn1SignatureAlg =
			new asn1.x509.AlgorithmIdentifier({'name': sigAlgName});

		const digest = this._cryptoSuite.hash(Buffer.from(csr.asn1CSRInfo.getEncodedHex(), 'hex'));
		const sig = this._cryptoSuite.sign(this, Buffer.from(digest, 'hex'));
		csr.hexSig = sig.toString('hex');

		csr.asn1Sig = new asn1.DERBitString({'hex': '00' + csr.hexSig});
		const seq = new asn1.DERSequence({'array': [csr.asn1CSRInfo, csr.asn1SignatureAlg, csr.asn1Sig]});
		csr.hTLV = seq.getEncodedHex();
		csr.isModified = false;
	}

	newCSRPEM(param) {
		const _KJUR_asn1_csr = asn1.csr;
		if (param.subject === undefined) {
			throw new Error('parameter subject undefined');
		}
		if (param.sbjpubkey === undefined) {
			throw new Error('parameter sbjpubkey undefined');
		}
		if (param.sigalg === undefined) {
			throw new Error('parameter sigalg undefined');
		}
		if (param.sbjprvkey === undefined) {
			throw new Error('parameter sbjpubkey undefined');
		}
		const ecdsa = new EC(this._cryptoSuite._ecdsaCurve);
		const pubKey = ecdsa.keyFromPublic(this._pub._ecpt);
		const csri = new _KJUR_asn1_csr.CertificationRequestInfo();
		csri.setSubjectByParam(param.subject);
		csri.setSubjectPublicKeyByGetKey({xy: pubKey.getPublic('hex'), curve: 'secp256r1'});
		if (param.ext !== undefined && param.ext.length !== undefined) {
			for (const ext of param.ext) {
				for (const key in ext) {
					csri.appendExtensionByName(key, ext[key]);
				}
			}
		}

		const csr = new _KJUR_asn1_csr.CertificationRequest({'csrinfo': csri});
		this.signCSR(csr, param.sigalg);

		const pem = csr.getPEMString();
		return pem;

	}

	/* implementation must include 'opts' as a parameter to this method */
	generateKey() {
		throw new Error('Not implemented');
	}

	generateEphemeralKey() {
		throw new Error('Not implemented');
	}

	generateCSR(subjectDN, extensions) {
		// check to see if this is a private key
		if (!this.isPrivate()) {
			throw new Error('A CSR cannot be generated from a public key');
		}

		const csr = this.newCSRPEM({
			subject: {str: asn1.x509.X500Name.ldapToOneline(subjectDN)},
			sbjpubkey: this._pub,
			sigalg: 'SHA256withECDSA',
			sbjprvkey: this,
			ext: extensions
		});
		return csr;
	}

	getSKI() {
		return this._ski.toString('hex');
	}

	isSymmetric() {
		return false;
	}

	isPrivate() {
		return (this._pub !== null);
	}

	getPublicKey() {
		return this._pub === null ? this : this._pub;
	}

	toBytes() {
		if (this._pub !== null) {
			throw new Error('toBytes: not allowed for private key');
		}

		return this._ecpt;
	}
};

module.exports = PKCS11_ECDSA_KEY;
